Ansibleのモジュール開発(基礎編)
はじめに
こんにちは、最近どっぷりサバゲーにハマってる藤本です。
Ansibleは数多くの標準モジュールを提供しています。標準モジュールを利用、もしくは標準モジュールを組み合わせて利用することで多くの設定を実現することができます。それでもケースによっては標準モジュールではできないことを実施したくなることがあります。そういった場合にAnsibleは独自のモジュールを実装することで任意の処理を行うことができます。今回は公式ドキュメントをなぞってみて、Ansibleで独自のモジュールを作成する基礎をご紹介します。
その他のAnsibleのモジュール開発シリーズは以下をご参照ください。
公式ドキュメントのページは以下となります。
http://docs.ansible.com/ansible/developing_modules.html
モジュール開発というとすごく大変そうなイメージがありますが、Ansibleのモジュール開発はいくつかのルールに則るだけで、作成するハードルは低いです。開発することよりもOSバージョン、ディストリビューションの差異の吸収、ミドルウェアバージョンアップへの追従などメンテナンスのコストが高いです。
なので、モジュール開発はゴリゴリに実装することはあまりオススメしません。先にも記載しましたが、可能な限り標準モジュールを利用、標準モジュールを組み合わせて実装することをオススメします。
開発言語
Ansible自体や標準モジュールはPythonで書かれていますが、ファイルI/O、標準出力ができれば、その他言語でも問題ありません。ただ、Pythonを利用すれば、モジュール開発にユーティリティライブラリを利用できるので、Pythonを利用することをオススメします。
本エントリでは分かりやすさを優先し、Bashでモジュールを記述します。
Python
python_module.py
#!/usr/bin/env python print '{}'
# ansible -m python_module -M . 10.255.0.100 10.255.0.100 | SUCCESS => { "changed": false }
Bash
bash_module.py
#!/bin/bash echo '{}'
# ansible -m bash_module -M . 10.255.0.100 10.255.0.100 | SUCCESS => { "changed": false }
Ruby
ruby_module.rb
#!/usr/bin/env ruby print '{}'
# ansible -m ruby_module -M . 10.255.0.100 10.255.0.100 | SUCCESS => { "changed": false }
JSON形式による標準出力
最低限覚えておくルールは一つです。モジュールのスクリプトでJSON形式で標準出力してください。JSON形式で標準出力しないとAnsibleによってモジュールはfailed判定となります。
plain.sh
#!/bin/sh echo "success"
# ansible -m plain -M . 10.255.0.100 10.255.0.100 | FAILED! => { "changed": false, "failed": true, "module_stderr": "", "module_stdout": "success\r\n", "msg": "MODULE FAILURE", "parsed": false }
Ansibleはモジュールから標準出力のJSONを受け取り、モジュールのステータス判定、実行結果の利用を行うことができます。
ステータス判定
ステータス判定はモジュールの実行による変更の有無や実行の成否を表します。それらを以下のキーで表します。
changed: true
or false
(変更の有無)
failed: true
or false
(モジュール実行の成否)
両キーともに必須のキーではありません。
changedは前節で空のJSONを返した時の結果から分かるように標準出力に指定しない場合、Ansibleによってfalseが設定されます。
failedは標準出力で渡さなくとも特に影響はありません。エラーと判断した時のみfailedにtrueを指定し、標準出力してください。またシェルスクリプト内でエラーや、スクリプト内で例外が発生した場合、Ansibleによってtrueが設定されます。
サンプルコードを見れば分かりやすいと思います。色が付いていた方が分かりやすいのでPlaybookで実施します。
# vi ok.sh #!/bin/sh echo '{"changed": false}' # vi changed.sh #!/bin/sh echo '{"changed": true}' # vi failed.sh #!/bin/sh echo '{"failed": true}' # vi linux.yml - hosts: 10.255.0.100 tasks: - ok: - changed: - failed:
このように標準出力の結果によって、ok
、changed
、failed
といったステータスが判断されています。逆に言えば、コマンドが失敗したからといって、failedが入っていないJSONを標準出力すれば、正常だと判断されてしまいます。そのようなことからもエラーハンドリングできる言語を選択するとよいでしょう。
冪等性の担保
ここで大事なことは冪等性を担保するのはモジュールの責務となります。
モジュールによってシステムへ変更を加えたことをAnsibleが判断してchangedをtrueで返すわけではありません。changedをtrueで返すのか、falseで返すのかはモジュールの実装によります。モジュール実行時に設定内容が既に設定済みであれば、設定変更は行わず、changedをfalseに指定し(もしくはchangedキー自体を指定せず)、標準出力してください。設定内容が設定されていなければ、設定処理を行い、changeをtrueに指定し、標準出力してください。設定処理が想定外の結果となった場合、failedをtrueに指定し、標準出力してください。それによりモジュールの利用者が把握できるように冪等性が担保されるように実装してください。
実行結果の利用
実行結果の利用はPlaybookでモジュールの実行結果をregisterで変数に受け取ることができ、返ってきた値を他のモジュールのオプションや、条件判断などに利用することができます。これらはモジュールからの標準出力を受け取っています。
こちらもサンプルを書いて理解しましょう。Playbookはchangedモジュールが返すJSONをresultという変数にセットし、debugモジュールでresultの内容を表示します。
# vi linux.yml - hosts: all tasks: - changed: register: result - debug: var=result ansible-playbook -M . linux.yml -v Using ansible.cfg as config file PLAY *************************************************************************** TASK [setup] ******************************************************************* ok: [10.255.0.100] TASK [changed] ***************************************************************** changed: [10.255.0.100] => {"changed": true} TASK [debug] ******************************************************************* ok: [10.255.0.100] => { "result": { "changed": true } } PLAY RECAP ********************************************************************* 10.255.0.100 : ok=3 changed=1 unreachable=0 failed=0
debugの出力にあるように標準出力のJSONを別のtaskに利用することができています。
モジュール配置パス
標準モジュール(Coreモジュール、Extrasモジュール)は<PYTHON_LIBRARY_PATH>/ansible/modules配下に配置されています。
# ls <PYTHON_LIBRARY_PATH>/ansible/modules __init__.py __init__.pyc core extras
例えば、RedHat系のパッケージ管理のyumモジュールは下記パスに配置されています。
/ansible/modules/core/packaging/os/yum.py
#!/usr/bin/python -tt # -*- coding: utf-8 -*- # (c) 2012, Red Hat, Inc # Written by Seth Vidal <skvidal at fedoraproject.org> # (c) 2014, Epic Games, Inc. :
独自モジュールを開発する時に上記の標準モジュールと同じパスに配置するわけにはいきません。モジュールは下記のパターンのパスから探しますので、いずれかに配置するよいでしょう。ちなみにこの順番の優先度でモジュールを探しているように見受けられました。
- カレントディレクトリのlibraryディレクトリ
プロジェクト固有のモジュールであれば、プロジェクトディレクトリ配下にlibraryディレクトリを作成して管理すべきでしょう。 -
環境変数のANSIBLE_LIBRARYにより指定されたパス
-
ansible.cfgのlibraryに指定されたパス
プロジェクトに関わらず共通で利用するモジュールであれば、2.か3.のどちらかで管理するのがよいでしょう。 -
ansible / ansible-playbookコマンドオプションの
--module-path
により指定されたパス
モジュールパス配下のディレクトリは考慮されず、モジュールのファイルだけの指定となります。例えば、a/b/c.pyというパスのモジュールでもcで指定可能です。逆に同じモジュール名(ファイル名)の場合、どちらか一方のみが実施されるので、モジュール名が重複しないように気をつけましょう。
引数を受け取る
Ansibleモジュールは引数をKey/Value形式で値を受け取ることができます。例えば、yumモジュールであれば、nameをKeyに、パッケージ名をValueをモジュール引数に指定することで、インストールするパッケージを指定します。
# ansible --become -m yum -a name=httpd 10.255.0.100 10.255.0.100 | SUCCESS => { "changed": true, "msg": "", "rc": 0, "results": [ "読み込んだプラグイン:fastestmirror\nLoading mirror speeds from cached hostfile\n * base: ftp.riken.jp\n * extras: ftp.riken.jp\n * updates: ftp.riken.jp\n依存性の解決をしています\n--> トランザクションの確認を実行しています。\n---> パッケージ httpd.x86_64 0:2.4.6-40.el7.centos.1 を インストール\n--> 依存性の処理をしています: httpd-tools = 2.4.6-40.el7.centos.1 のパッケージ: httpd-2.4.6-40.el7.centos.1.x86_64\n--> 依存性の処理をしています: /etc/mime.types のパッケージ: httpd-2.4.6-40.el7.centos.1.x86_64\n--> 依存性の処理をしています: libaprutil-1.so.0()(64bit) のパッケージ: httpd-2.4.6-40.el7.centos.1.x86_64\n--> 依存性の処理をしています: libapr-1.so.0()(64bit) のパッケージ: httpd-2.4.6-40.el7.centos.1.x86_64\n--> トランザクションの確認を実行しています。\n---> パッケージ apr.x86_64 0:1.4.8-3.el7 を インストール\n---> パッケージ apr-util.x86_64 0:1.5.2-6.el7 を インストール\n---> パッケージ httpd-tools.x86_64 0:2.4.6-40.el7.centos.1 を インストール\n---> パッケージ mailcap.noarch 0:2.1.41-2.el7 を インストール\n--> 依存性解決を終了しました。\n\n依存性を解決しました\n\n================================================================================\n Package アーキテクチャー\n バージョン リポジトリー 容量\n================================================================================\nインストール中:\n httpd x86_64 2.4.6-40.el7.centos.1 updates 2.7 M\n依存性関連でのインストールをします:\n apr x86_64 1.4.8-3.el7 base 103 k\n apr-util x86_64 1.5.2-6.el7 base 92 k\n httpd-tools x86_64 2.4.6-40.el7.centos.1 updates 82 k\n mailcap noarch 2.1.41-2.el7 base 31 k\n\nトランザクションの要約\n================================================================================\nインストール 1 パッケージ (+4 個の依存関係のパッケージ)\n\n総ダウンロード容量: 3.0 M\nインストール容量: 10 M\nDownloading packages:\n--------------------------------------------------------------------------------\n合計 4.8 MB/s | 3.0 MB 00:00 \nRunning transaction check\nRunning transaction test\nTransaction test succeeded\nRunning transaction\n インストール中 : apr-1.4.8-3.el7.x86_64 1/5 \n インストール中 : apr-util-1.5.2-6.el7.x86_64 2/5 \n インストール中 : httpd-tools-2.4.6-40.el7.centos.1.x86_64 3/5 \n インストール中 : mailcap-2.1.41-2.el7.noarch 4/5 \n インストール中 : httpd-2.4.6-40.el7.centos.1.x86_64 5/5 \n 検証中 : mailcap-2.1.41-2.el7.noarch 1/5 \n 検証中 : httpd-2.4.6-40.el7.centos.1.x86_64 2/5 \n 検証中 : apr-util-1.5.2-6.el7.x86_64 3/5 \n 検証中 : apr-1.4.8-3.el7.x86_64 4/5 \n 検証中 : httpd-tools-2.4.6-40.el7.centos.1.x86_64 5/5 \n\nインストール:\n httpd.x86_64 0:2.4.6-40.el7.centos.1 \n\n依存性関連をインストールしました:\n apr.x86_64 0:1.4.8-3.el7 apr-util.x86_64 0:1.5.2-6.el7 \n httpd-tools.x86_64 0:2.4.6-40.el7.centos.1 mailcap.noarch 0:2.1.41-2.el7 \n\n完了しました!\n" ] }
引数はモジュールのスクリプトとともにargsというファイルで渡されます。
こちらもサンプルコードを書いて理解しましょう。
library/option.sh
#!/bin/bash source `dirname $0`/args echo "{\"message\":\"$message\"}"
# ansible -m option -a message=hello 10.255.0.100 10.255.0.100 | SUCCESS => { "changed": false, "message": "hello" }
ansibleコマンドで引数として渡したmessage変数がスクリプトに渡っていることを確認できます。
Playbookでは以下のように記述します。
- hosts: all tasks: - option: message=hello
ちなみにPythonのモジュールであれば、このようなことは意識せずとも引数を利用することが可能です。
まとめ
いかがでしたでしょうか?
今回はモジュールの基礎から理解するということで分かりやすくBashで記載し、モジュールがどのように使われているのかを理解することができました。次回は実践編ということでPythonのUtilityライブラリを使ったり、OS/ディストリビューションの差異を吸収する方法をお伝えしたいと思います。